package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import com.atguigu.gmall.index.config.BloomFilterConfig;
import com.google.common.hash.BloomFilter;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Component
@Aspect//这是个切面类
public class GmallCacheAspect {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RBloomFilter bloomFilter;
    @Pointcut("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public void poincut(){}
    @Around("poincut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取方法对象
        Method method = signature.getMethod();
        //获取目标方法的注解对象
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);
        String prefix = gmallCache.prefix();//获取缓存前缀
        //目标方法的形参
        Object[] args = joinPoint.getArgs();
        //形参值使用逗号分割合并成字符串
        String stringKey = StringUtils.join(args, ",");
        //合并缓存的key
        String key = prefix + stringKey;
        //先查询缓存，如果缓存可以命中，则直接返回
        String json = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)){
            //把命中的json字符串反序列化成方法的返回类型
            return JSON.parseObject(json,method.getReturnType());
        }
        //为了防止缓存穿透，使用布隆过滤器
        if (!bloomFilter.contains(key)){
            return null;
        }


        //为了防止缓存击穿，添加了分布式锁
        RLock lock = redissonClient.getLock(gmallCache.lock() + stringKey);
        lock.lock();
        try {
            //当前线程再获取分布式锁的过程中，可能有其他线程已经获取了分布式锁，此时再次查询缓存，如果可以命中，直接返回
            String json2 = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)){
                return JSON.parseObject(json2,method.getReturnType());
            }
            //目标方法
            Object result = joinPoint.proceed(args);

            //把目标方法的返回结构放入缓存，并释放分布式锁
            //给缓存时间添加随机值
            int timeOut = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
            redisTemplate.opsForValue().set(key,JSON.toJSONString(result),timeOut, TimeUnit.MINUTES);


            return result;
        } finally {
            lock.unlock();
        }
    }
}
